﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/3/9 16:04:19
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ImageK.Gui;
using ImageK.Measure;
using ImageK.Process;

namespace ImageK.Plugin
{
	/** Implements the Image/Stacks/Make Montage command. */
	public class MontageMaker : PlugIn
	{
	    private static int columns, rows, first, last, inc, borderWidth;
	    private static double scale;
	    private static bool label;
	    private static bool useForegroundColor;
	    private static int saveID;
	    private static int saveStackSize;
	    private const int defaultFontSize = 12;
	    private int fontSize = defaultFontSize;
	    private bool hyperstack;

	    public void run(string arg)
	    {
		    ImagePlus imp = WindowManager.getCurrentImage();
		    if (imp == null || imp.getStackSize() == 1)
		    {
			    error("Stack required");
			    return;
		    }
		    hyperstack = imp.isHyperStack();
		    if (hyperstack && imp.getNSlices() > 1 && imp.getNFrames() > 1)
		    {
			    error("5D hyperstacks are not supported");
			    return;
		    }
		    int channels = imp.getNChannels();
		    if (!hyperstack && imp.isComposite() && channels > 1)
		    {
			    int channel = imp.getChannel();
			    CompositeImage ci = (CompositeImage)imp;
			    int mode = ci.getMode();
			    if (mode == IJ.COMPOSITE)
				    ci.setMode(IJ.COLOR);
			    ImageStack stack = new ImageStack(imp.getWidth(), imp.getHeight());
			    for (int c = 1; c <= channels; c++)
			    {
				    imp.setPosition(c, imp.getSlice(), imp.getFrame());
				    Image img = imp.getImage();
				    stack.addSlice(null, new ColorProcessor(img));
			    }
			    if (ci.getMode() != mode)
				    ci.setMode(mode);
			    imp.setPosition(channel, imp.getSlice(), imp.getFrame());
			    Calibration cal = imp.getCalibration();
			    imp = new ImagePlus(imp.getTitle(), stack);
			    imp.setCalibration(cal);
		    }
		    makeMontage(imp);
		    imp.updateImage();
		    saveID = imp.getID();
		    IJ.register(typeof(MontageMaker));
	    }

        public void makeMontage(ImagePlus imp)
        {
	        int nSlices = imp.getStackSize();
	        if (hyperstack)
	        {
		        nSlices = imp.getNSlices();
		        if (nSlices == 1)
			        nSlices = imp.getNFrames();
	        }
	        bool macro = Macro.GetOptions() != null;
	        if (macro || columns == 0 || !(imp.getID() == saveID || nSlices == saveStackSize))
	        {
		        columns = (int)Math.Sqrt(nSlices);
		        rows = columns;
		        int n = nSlices - columns * rows;
		        if (n > 0) columns += (int)Math.Ceiling((double)n / rows);
		        scale = 1.0;
		        if (imp.getWidth() * columns > 800)
			        scale = 0.5;
		        if (imp.getWidth() * columns > 1600)
			        scale = 0.25;
		        inc = 1;
		        first = 1;
		        last = nSlices;
	        }
	        if (macro)
	        {
		        fontSize = defaultFontSize;
		        borderWidth = 0;
		        label = false;
		        useForegroundColor = false;
	        }
	        saveStackSize = nSlices;

	        GenericDialog gd = new GenericDialog("Make Montage");
	        gd.addNumericField("Columns:", columns, 0);
	        gd.addNumericField("Rows:", rows, 0);
	        gd.addNumericField("Scale factor:", scale, 2);
	        if (!hyperstack)
	        {
		        gd.addNumericField("First slice:", first, 0);
		        gd.addNumericField("Last slice:", last, 0);
	        }
	        gd.addNumericField("Increment:", inc, 0);
	        gd.addNumericField("Border width:", borderWidth, 0);
	        gd.addNumericField("Font size:", fontSize, 0);
	        gd.addCheckbox("Label slices", label);
	        gd.addCheckbox("Use foreground color", useForegroundColor);
	        gd.showDialog();
	        if (gd.wasCanceled())
		        return;
	        columns = (int)gd.getNextNumber();
	        rows = (int)gd.getNextNumber();
	        scale = gd.getNextNumber();
	        gd.setSmartRecording(true);
	        if (!hyperstack)
	        {
		        first = (int)gd.getNextNumber();
		        last = (int)gd.getNextNumber();
	        }
	        inc = (int)gd.getNextNumber();
	        borderWidth = (int)gd.getNextNumber();
	        fontSize = (int)gd.getNextNumber();
	        if (borderWidth < 0) borderWidth = 0;
	        if (first < 1) first = 1;
	        if (last > nSlices) last = nSlices;
	        if (first > last)
	        { first = 1; last = nSlices; }
	        if (inc < 1) inc = 1;
	        if (gd.invalidNumber())
	        {
		        error("Invalid number");
		        return;
	        }
	        label = gd.getNextBoolean();
	        useForegroundColor = gd.getNextBoolean();
	        ImagePlus imp2 = null;
	        if (hyperstack)
		        imp2 = makeHyperstackMontage(imp, columns, rows, scale, inc, borderWidth, label);
	        else
		        imp2 = makeMontage2(imp, columns, rows, scale, first, last, inc, borderWidth, label);
	        if (imp2 != null)
		        imp2.Show();
	        if (macro)
	        {
		        fontSize = defaultFontSize;
		        borderWidth = 0;
		        label = false;
		        useForegroundColor = false;
		        columns = 0;
	        }
        }

        /** Creates a montage and displays it. */
        public void makeMontage(ImagePlus imp, int columns, int rows, double scale, int first, int last, int inc, int borderWidth, bool labels)
        {
	        ImagePlus imp2 = makeMontage2(imp, columns, rows, scale, first, last, inc, borderWidth, labels);
	        imp2.Show();
        }

        /** Creates a montage and returns it as an ImagePlus. */
        public ImagePlus makeMontage2(ImagePlus imp, int columns, int rows, double scale, int first, int last, int inc, int borderWidth, bool labels)
        {
	        int stackWidth = imp.getWidth();
	        int stackHeight = imp.getHeight();
	        int nSlices = imp.getStackSize();
	        int width = (int)(stackWidth * scale);
	        int height = (int)(stackHeight * scale);
	        int montageWidth = width * columns + borderWidth * (columns - 1);
	        int montageHeight = height * rows + borderWidth * (rows - 1);
	        ImageProcessor ip = imp.getProcessor();
	        ImageProcessor montage = ip.createProcessor(montageWidth, montageHeight);
	        ImagePlus imp2 = new ImagePlus("Montage", montage);
	        imp2.setCalibration(imp.getCalibration());
	        montage = imp2.getProcessor();
	        Color fgColor = Color.White;
	        Color bgColor = Color.Black;
	        if (useForegroundColor)
	        {
		        fgColor = ToolBar.getForegroundColor();
		        bgColor = ToolBar.getBackgroundColor();
	        }
	        else
	        {
		        bool whiteBackground = false;
		        if ((ip is ByteProcessor) || (ip is ColorProcessor)) 
                {
	                ip.setRoi(0, stackHeight - 12, stackWidth, 12);
	                ImageStatistics stats = ImageStatistics.getStatistics(ip, Measurements.MODE, null);
	                ip.resetRoi();
	                whiteBackground = stats.mode >= 200;
	                if (imp.isInvertedLut())
		                whiteBackground = !whiteBackground;
                }
                if (whiteBackground)
                {
	                fgColor = Color.Black;
	                bgColor = Color.White;
                }
		    }
		    if (double.IsNaN(ToolBar.getBackgroundValue()))
	            montage.setColor(bgColor);
            else
	            montage.setGlobalBackgroundColor();
            montage.fill();
            if (double.IsNaN(ToolBar.getForegroundValue()))
	            montage.setColor(fgColor);
            else
	            montage.setGlobalForegroundColor();
            montage.setFont(new Font("SansSerif", fontSize, FontStyle.Regular));
            montage.setAntialiasedText(true);
            ImageStack stack = imp.getStack();
            int x = 0;
            int y = 0;
            ImageProcessor aSlice;
            int slice = first;
            while (slice <= last)
            {
	            aSlice = stack.getProcessor(slice);
	            if (scale != 1.0)
	            {
		            aSlice.setInterpolationMethod(ImageProcessor.BILINEAR);
		            bool averageWhenDownSizing = width < 200;
		            aSlice = aSlice.resize(width, height, averageWhenDownSizing);
	            }
	            montage.insert(aSlice, x, y);
	            string label = stack.getShortSliceLabel(slice);
	            if (labels)
		            drawLabel(montage, slice, label, x, y, width, height, borderWidth);
	            x += width + borderWidth;
	            if (x >= montageWidth)
	            {
		            x = 0;
		            y += height + borderWidth; ;
		            if (y >= montageHeight)
			            break;
	            }
	            IJ.showProgress((double)(slice - first) / (last - first));
	            slice += inc;
            }
            if (borderWidth > 0)
            {
	            for (x = width; x < montageWidth; x += width + borderWidth)
	            {
		            montage.setRoi(x, 0, borderWidth, montageHeight);
		            montage.fill();
	            }
	            for (y = height; y < montageHeight; y += height + borderWidth)
	            {
		            montage.setRoi(0, y, montageWidth, borderWidth);
		            montage.fill();
	            }
            }
            IJ.showProgress(1.0);
            Calibration cal = imp2.getCalibration();
            if (cal.scaled())
            {
	            cal.pixelWidth /= scale;
	            cal.pixelHeight /= scale;
            }
            imp2.setProperty("Info", "xMontage=" + columns + "\nyMontage=" + rows + "\n");
            return imp2;
	    }
		
	    /** Creates a hyperstack montage and returns it as an ImagePlus. */
	    private ImagePlus makeHyperstackMontage(ImagePlus imp, int columns, int rows, double scale, int inc, int borderWidth, bool labels)
        {
	        ImagePlus[] channels = ChannelSplitter.split(imp);
	        int n = channels.Length;
	        ImagePlus[] montages = new ImagePlus[n];
	        for (int i = 0; i < n; i++)
	        {
		        int last = channels[i].getStackSize();
		        montages[i] = makeMontage2(channels[i], columns, rows, scale, 1, last, inc, borderWidth, labels);
	        }
	        ImagePlus montage = (new RGBStackMerge()).mergeHyperstacks(montages, false);
	        montage.setCalibration(montages[0].getCalibration());
	        montage.setTitle("Montage");
	        return montage;
        }

        private void error(string msg)
        {
	        IJ.error("Make Montage", msg);
        }

        void drawLabel(ImageProcessor montage, int slice, string label, int x, int y, int width, int height, int borderWidth)
        {
	        if (label != null && !label.Equals("") && montage.getStringWidth(label) >= width)
	        {
		        do
		        {
			        label = label.Substring(0, label.Length - 1);
		        } while (label.Length > 1 && montage.getStringWidth(label) >= width);
	        }
	        if (label == null || label.Equals(""))
		        label = "" + slice;
	        int swidth = montage.getStringWidth(label);
	        x += width / 2 - swidth / 2;
	        y -= borderWidth / 2;
	        y += height;
	        montage.drawString(label, x, y);
        }

        public void setFontSize(int fontSize)
        {
	        this.fontSize = fontSize;
        }
	
    }
}
